package com.qlogic.commons.kernel;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.Collection;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Set;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import com.qlogic.commons.exceptions.BaseException;
import com.qlogic.commons.exceptions.BaseRuntimeException;
import com.qlogic.commons.platform.Service;
import com.qlogic.commons.platform.ServiceDesactivationException;
import com.qlogic.commons.platform.ServiceStopingException;
import com.qlogic.commons.resources.EmptyResourceNameException;
import com.qlogic.commons.resources.Resource;
import com.qlogic.commons.resources.ResourceNotFoundException;
import com.qlogic.commons.resources.WrongResourceNameException;
import com.qlogic.commons.resources.xml.ResourceReader;
import com.qlogic.commons.utils.jee.JNDIUtils;

/**
 * @version $Date: 2005/07/20 10:59:15 $, $Revision: 1.2 $, branche-$Name:  $
 * @author $Author: mlo $
 */
public class Kernel implements Serializable {
	
	/**
	 * 
	 */
	private static final long serialVersionUID = 3041284820459503809L;

	public static final String ROOT_RESOURCE = "root";
	public static final String ROOT_CLASSLOADER = "system";
	public static final String KERNEL_JNDI_LOCATION = "qlogic/app/kernel";
	
	public static final String STATUS_STOPED = "stoped";
	public static final String STATUS_STARTED = "started";
	public static final String STATUS_START_IN_PROGRESS = "startInProgress";
	public static final String STATUS_STOP_IN_PROGRESS = "stopInProgress";
	
	private static Hashtable additionalClassLoaders = new Hashtable (); 
	
	protected String config;
	protected String status = STATUS_STOPED;
	protected Resource rootResource;
	protected BasicFactory factory;
	protected ResourceReader resourceReader;
	
	protected static boolean standalone = true;
	
	protected static Kernel kernel;
	
	
	public BasicFactory getFactory() {
		return factory;
	}

	public Kernel () throws KernelException {
		this (null);
	}
	
	public Kernel (File config) throws KernelException {
		this (config, true);
	}
	
	public Kernel (File config, boolean _standalone) throws KernelException {
		additionalClassLoaders.put (ROOT_CLASSLOADER, Thread.currentThread().getContextClassLoader ());
		standalone = _standalone;
		if (config != null) {
			this.config = config.getAbsolutePath ();
		}
		if (standalone) {
			kernel = this;
		} else {
			Context context = null;
			try {
				context = new InitialContext ();
				JNDIUtils.bind (context, KERNEL_JNDI_LOCATION, this);
			} catch (NamingException e) {
				throw new KernelException (e);
			}
		}
	}
	
	public String getConfig() {
		return config;
	}

	public void setConfig(String config) {
		this.config = config;
	}

	private Resource internalGetRootResource () throws KernelException {
		if (rootResource == null) {
			throw new KernelException ("Kernel not yet initialized");
		}
		return rootResource;
	}

	public static Resource getRootResource () throws KernelException {
		return getCurrentInstance ().internalGetRootResource();
	}

	public synchronized void start (Hashtable parameters) throws KernelException {
		if (!status.equals (STATUS_STOPED)) {
			throw new KernelException ("There is an action currently running, status [" + status + "]");
		}
		status = STATUS_START_IN_PROGRESS;
		try {
			File configFile = null;
			if (config != null) {
				configFile = new File (config);
			}
			factory = new BasicFactory (getRootClassLoader(), configFile);
			String rootResourcePath = factory.getProperty (BasicFactory.ROOT_RESOURCE_FILE);
			System.out.println ("[Kernel] root resource found (" + rootResourcePath + ")");
			if (rootResourcePath == null) {
				rootResource = new Resource (ROOT_RESOURCE);
				return;
			}
			InputStream is = null;
			boolean isFile = true;
			if (!new File (rootResourcePath).exists ()) {
				URL url = Kernel.class.getResource (rootResourcePath);
				is = url.openStream ();
				isFile = false;
			} else {
				is = new FileInputStream (new File (rootResourcePath));
			}
			resourceReader = (ResourceReader)factory.getInstance (ResourceReader.class);
			resourceReader.setClassLoader (getRootClassLoader());
			if (isFile) {
				rootResource = resourceReader.read (new File (rootResourcePath));
			} else {
				rootResource = resourceReader.read (is);
			}
			
			if (parameters == null) {
				parameters = new Hashtable ();
				parameters.put (BasicFactory.ROOT_CONFIG_PATH, 
						rootResourcePath.substring (0, rootResourcePath.lastIndexOf ("/")));
			}
			rootResource.resolveReferences (parameters);
		} catch (Throwable e) {
			throw new KernelException (e);
		}

		Resource evalRes = null;
		try {
			evalRes = rootResource.getResource ("evaluated-resources");
		} catch (BaseException e) {
		}
		
		if (evalRes != null) {
			try {
				Collection servicePaths = (Collection)evalRes.getValue ();
				Iterator iterator = servicePaths.iterator();
				while (iterator.hasNext ()) {
					String servicePath = (String)iterator.next ();
					System.out.println ("Evaluation of ressource [" + servicePath + "]");
					Object value = rootResource.getResource (servicePath.trim ()).getValue ();
					System.out.println ("\tResource [" + servicePath + "] evaluated to [" + value + "]");
				}
			} catch (Throwable e) {
				throw new KernelException (e);
			}
		}
		status = STATUS_STARTED;
	}
	
	public synchronized void start () throws KernelException {
		start (null);
	}
	
	public synchronized void stop () throws KernelException {
		if (!status.equals (STATUS_STARTED)) {
			throw new KernelException ("Can not stop kernel, status [" + status + "]");
		}
		status = STATUS_STOP_IN_PROGRESS;
		try {
			internalStopService (rootResource);
		} catch (BaseException e) {
			throw new KernelException (e);
		} 
		rootResource = null;
		status = STATUS_STOPED;
	}
	
	private void internalStopService (Resource serviceResource) throws ServiceDesactivationException, ServiceStopingException, EmptyResourceNameException, WrongResourceNameException, ResourceNotFoundException {
		if (serviceResource.isSingleton () && 
				serviceResource.getResult () != null &&
				Service.class.isAssignableFrom (serviceResource.getResult().getClass ())) {
			((Service)serviceResource.getResult()).desactivate();
			((Service)serviceResource.getResult()).stop();
		}
		if (serviceResource.hasInnerResources ()) {
			Set names = serviceResource.getResourceKeys ();
			Iterator iterator = names.iterator ();
			while (iterator.hasNext ()) {
				String resourceName = (String)iterator.next ();
				internalStopService (serviceResource.getResource (resourceName));
			}
		}
	}
	
	public static void stopService (Resource serviceResource) throws ServiceDesactivationException, ServiceStopingException, EmptyResourceNameException, WrongResourceNameException, ResourceNotFoundException, KernelException {
		getCurrentInstance().internalStopService (serviceResource);
	}
	
	private Service internalGetService(String path) throws KernelException {
		Object object = internalGetObject (path); 
		if (object == null || !Service.class.isAssignableFrom (object.getClass ())) {
			throw new KernelException ("ressource [" + path + "] doesn't correspond to a service");
		}
		return (Service)object;
	}

	public static Service getService(String path) throws KernelException {
		return getCurrentInstance ().internalGetService(path);
	}

	private Object internalGetObject (String path) throws KernelException {
		try {
			return internalGetResource(path).getValue ();
		} catch (BaseException e) {
			throw new KernelException (e);
		} 
	}

	public static Object getObject (String path) throws KernelException {
		return getCurrentInstance ().internalGetObject (path); 
	}

	private Resource internalGetResource(String path) throws KernelException {
		/*
		if (!status.equals (STATUS_STARTED)) {
			throw new KernelException ("Kernel not yet started, status [" + status + "]");
		}
		*/
		try {
			return rootResource.getResource(path);
		} catch (BaseException e) {
			throw new KernelException (e);
		} 
	}

	public static Resource getResource(String path) throws KernelException {
		return getCurrentInstance().internalGetResource (path);
	}

	public ClassLoader getRootClassLoader() {
		return (ClassLoader)additionalClassLoaders.get (ROOT_CLASSLOADER);
	}

	public void setRootClassLoader(ClassLoader classLoader) {
		additionalClassLoaders.put (ROOT_CLASSLOADER, classLoader);
	}
	
	public void registerAddionalClassLoader (String key, ClassLoader classLoader) {
		additionalClassLoaders.put (key, classLoader);
	}

	public ClassLoader getClassLoader (String key) {
		return (ClassLoader)additionalClassLoaders.get (key);
	}

	public Hashtable getAdditionalClassLoaders () {
		return additionalClassLoaders;
	}

	public ResourceReader getResourceReader() {
		return resourceReader;
	}

	public String getStatus() {
		return status;
	}
	
	public Class loadClass (String classLoaderKey, String className) throws ClassNotFoundException {
		ClassLoader classLoader = getClassLoader (classLoaderKey);
		if (classLoader == null) {
			throw new BaseRuntimeException ("Module Class Loader [" + classLoaderKey + "] not found. Verify your registered modules !");
		}
		return getClassLoader (classLoaderKey).loadClass (className);
	}
	
	public Object newInstance (String classLoaderKey, String className) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
		return loadClass (classLoaderKey, className).newInstance ();
	}
	
	public Object executeMethod (String classLoaderKey, String className, String methodName, Class [] args, Object [] values) throws ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, InstantiationException {
		Class clazz = loadClass (classLoaderKey, className);
		Method method = clazz.getMethod (methodName, args);
		int modifiers = method.getModifiers ();
		if (Modifier.isStatic (modifiers)) {
			return method.invoke (null, values);
		} else {
			return method.invoke (clazz.newInstance (), values);
		}
	}
	
	public static Kernel getCurrentInstance () throws KernelException {
		if (standalone) {
			return kernel;
		} else {
			try {
				return (Kernel)JNDIUtils.lookup (new InitialContext (), KERNEL_JNDI_LOCATION, true);
			} catch (NamingException e) {
				throw new KernelException (e);
			}
		}
	}
	
}
